﻿//Copyright 2011 - Jonathan Allen

#if ATTRIBUTE_VALIDATION && !NET45

using System;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Diagnostics.Contracts;

namespace Granite.DataAnnotations
{
    /// <summary>
    /// This validates a property by ensuring the length is less than or equal to the indicated amount.
    /// This can be used on strings and IEnumerables
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public sealed class MaxLengthAttribute : ValidationAttribute
    {
        readonly int m_MaxLength;

        /// <summary>
        /// This validates a property by ensuring the length is less than or equal to the indicated amount.
        /// This can be used on strings and IEnumerables
        /// </summary>
        /// <param name="maxLength">Maximum length in characters or items</param>
        public MaxLengthAttribute(int maxLength)
        {
            if (maxLength <= 0)
                throw new ArgumentOutOfRangeException("maxLength", "Maximum length must be greater than zero.");
            Contract.EndContractBlock();

            m_MaxLength = maxLength;
        }

        /// <summary>
        /// Maximum length in characters or items
        /// </summary>
        public int MaxLength
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() > 0);

                return m_MaxLength;
            }
        }


        /// <summary>
        /// Validates the specified value with respect to the current validation attribute.
        /// </summary>
        /// <returns>
        /// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class. 
        /// </returns>
        /// <param name="value">
        /// The value to validate.
        /// </param>
        /// <param name="validationContext">
        /// The context information about the validation operation.
        /// </param>
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "MaxLengthAttribute")]
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            Contract.Ensures(Contract.Result<ValidationResult>() != null);

			Contract.Assume(validationContext != null);

            if (value == null)
                return ValidationResult.Success;

            if (value is string)
                return CreateResult(validationContext, ((string)value).Length);

            if (value is ICollection)
                return CreateResult(validationContext, ((ICollection)value).Count);

            if (value is IEnumerable)
            {
                var enumerator = ((IEnumerable)value).GetEnumerator();
                try
                {
                    var count = 0;
                    while (enumerator.MoveNext())
                        count++;

                    Contract.Assume(validationContext != null);
                    return CreateResult(validationContext, count);
                }
                finally
                {
                    if (enumerator is IDisposable)
                        ((IDisposable)enumerator).Dispose();
                }

            }

            throw new ArgumentException("The data type" + value.GetType().FullName + " is not supported by the MaxLengthAttribute", "value");
        }

        ValidationResult CreateResult(ValidationContext validationContext, int count)
        {
			Contract.Requires(validationContext != null);
            Contract.Ensures(Contract.Result<ValidationResult>() != null);

            if (count > MaxLength)
            {
                return new ValidationResult(ErrorMessage ?? validationContext.MemberName + " cannot be more than " + MaxLength + " characters long", new string[] { validationContext.MemberName });
            }
            else return ValidationResult.Success;
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        [ContractInvariantMethod]
        void ObjectInvariant()
        {
            Contract.Invariant(m_MaxLength > 0);
        }

    }
}

#endif
